package com.sftz.modules.minapp.paper.service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.SqlManager;
import org.nutz.dao.Sqls;
import org.nutz.dao.entity.Record;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Files;
import org.nutz.mvc.annotation.Param;
import org.nutz.mvc.upload.TempFile;

import com.sftz.assets.entities.question.PaperAnswerInfoEntity;
import com.sftz.assets.entities.question.PaperEntity;
import com.sftz.assets.entities.question.PaperFileQuestionEntity;
import com.sftz.assets.entities.question.PaperQuestionAnswerEntity;
import com.sftz.assets.entities.question.PaperQuestionAnswerFileEntity;
import com.sftz.assets.entities.question.PaperQuestionEntity;
import com.sftz.assets.enums.AnswerStatusEnum;
import com.sftz.assets.enums.ObjectiveEnum;
import com.sftz.assets.enums.QuestionAnswerResourceTypeEnum;
import com.sftz.assets.enums.QuestionTypeEnum;
import com.sftz.assets.enums.YesOrNoEnum;
import com.sftz.assets.utils.QuestionUtil;
import com.sftz.framework.rersult.MobileResult;
import com.sftz.framework.service.aliyun.AliyunVideoDesc;
import com.sftz.framework.service.aliyun.AliyunVideoUploadService;
import com.sftz.framework.service.qiniu.QiniuDownloadService;
import com.sftz.framework.util.CompareUtil;
import com.sftz.framework.util.WeappHtmlUtil;
import com.sftz.modules.common.dto.FillInStemDto;
import com.sftz.modules.common.service.CourseScoreSettingsService;
import com.sftz.modules.minapp.paper.dto.PaperAnswerDto;
import com.sftz.modules.minapp.paper.dto.PaperAnswerQuestionDto;
import com.sftz.modules.minapp.paper.dto.PaperEntryDto;
import com.sftz.modules.minapp.paper.dto.PaperModuleInfoDto;
import com.sftz.modules.minapp.paper.dto.PaperObjectiveReviewDto;
import com.sftz.modules.minapp.paper.dto.PaperQuestionOptionDto;
import com.sftz.modules.minapp.paper.dto.PaperSaveAnswerDto;
import com.sftz.modules.minapp.paper.form.PaperAnswerDetailForm;
import com.sftz.modules.minapp.paper.form.PaperAnswerFileForm;
import com.sftz.modules.minapp.paper.form.PaperCommitForm;
import com.sftz.modules.minapp.paper.form.PaperDeleteAnswerFileForm;
import com.sftz.modules.minapp.paper.form.PaperEntryForm;
import com.sftz.modules.minapp.paper.form.PaperResultForm;
import com.sftz.modules.minapp.paper.form.PaperSaveQuestAnswerForm;
import com.sftz.modules.minapp.user.cache.RedissionUserCache;
import com.uxuexi.core.common.util.BeanUtil;
import com.uxuexi.core.common.util.DateUtil;
import com.uxuexi.core.common.util.Util;
import com.uxuexi.core.db.dao.IDbDao;
import com.uxuexi.core.db.util.DbSqlUtil;

@IocBean
public class PaperViewService {

	@Inject
	protected IDbDao dbDao;

	@Inject
	protected SqlManager sqlManager;

	@Inject
	private QiniuDownloadService qiniuDownloadService;

	@Inject
	private AliyunVideoUploadService aliyunVideoUploadService;

	@Inject
	protected CourseScoreSettingsService courseScoreSettingsService;

	public Map<String, Object> entry(final PaperEntryForm form) {
		String token = form.getToken();
		Integer userId = RedissionUserCache.getUserId(token);

		Integer paperId = form.getPaperId();

		//1 查询试卷的基础信息
		Sql sql = Sqls.create(sqlManager.get("minapp_paper_baseInfo"));
		sql.setParam("userId", userId);
		sql.setParam("isPost", YesOrNoEnum.YES.intKey());
		sql.setParam("studentId", userId);
		sql.setParam("marking", AnswerStatusEnum.marking.intKey());
		sql.setParam("finish", AnswerStatusEnum.finish.intKey());
		sql.setParam("paperId", paperId);
		List<PaperEntryDto> lst = DbSqlUtil.query(dbDao, PaperEntryDto.class, sql);
		if (!Util.isEmpty(lst) && lst.size() == 1) {
			PaperEntryDto dto = lst.get(0);

			//设置延迟时间
			Date postDate = dto.getPostDate();
			if (!Util.isEmpty(postDate)) {
				dto.setEndDate(postDate);
			}

			//2 查询模块信息
			Sql moduleSql = Sqls.create(sqlManager.get("minapp_paper_moduleInfo"));
			moduleSql.setParam("paperId", paperId);
			List<PaperModuleInfoDto> moduleList = DbSqlUtil.query(dbDao, PaperModuleInfoDto.class, moduleSql);
			dto.setModuleList(moduleList);

			/*
			 * 3，查询作答记录（不提交就不出现在页面）
			 *    如果答题记录已经过了答题时间，则自动提交，提交次数+1
			 */
			Map<String, Object> rmap = allRecord(form);
			@SuppressWarnings("unchecked")
			List<PaperAnswerInfoEntity> allRecords = (List<PaperAnswerInfoEntity>) rmap.get(MobileResult.DATA_KEY);

			//未提交的
			List<PaperAnswerInfoEntity> unsubmitted = new ArrayList<>();
			//已提交的
			List<PaperAnswerInfoEntity> submitted = new ArrayList<>();
			if (!Util.isEmpty(allRecords)) {
				for (PaperAnswerInfoEntity r : allRecords) {
					Integer stat = r.getStatus();

					String readoverName = "";
					if (null != stat && AnswerStatusEnum.marking.intKey() == stat) {
						readoverName = "已批阅";
					} else {
						readoverName = "未批阅";
					}
					r.setReadoverName(readoverName);

					if (AnswerStatusEnum.uncommit.intKey() == stat || AnswerStatusEnum.start.intKey() == stat) {
						unsubmitted.add(r);
					} else {
						submitted.add(r);
					}
				}
			}

			if (!Util.isEmpty(unsubmitted)) {
				if (unsubmitted.size() > 1) {
					return MobileResult.error("未提交的作答记录最多只能有一条", null);
				} else {
					PaperAnswerInfoEntity unsubmit = unsubmitted.get(0);

					Date now = DateUtil.nowDate();

					//答题截止时间
					PaperEntity paper = new PaperEntity();
					BeanUtil.copyProperties(dto, paper);
					paper.setEndTime(dto.getEndDate());
					Date answerEndTime = QuestionUtil.calAnswerEndTime(paper, unsubmit);

					if (now.after(answerEndTime)) {
						submitAnswerRecord(unsubmit, answerEndTime);

						//+1
						int answerSize = dto.getAnswerSize() + 1;
						dto.setAnswerSize(answerSize);
						submitted.add(unsubmit);
					}
				}
			}

			//设置作答状态
			Date paperStartDate = dto.getStartDate();
			Date paperEndDate = dto.getEndDate();
			int answerNumber = dto.getAnswerNumber();
			int answerSize = dto.getAnswerSize();
			dto.setStartStatus(QuestionUtil.startStatus(paperStartDate, paperEndDate, answerNumber, answerSize));

			//提交时间倒排
			Collections.sort(submitted, new Comparator<PaperAnswerInfoEntity>() {
				@Override
				public int compare(PaperAnswerInfoEntity o1, PaperAnswerInfoEntity o2) {
					if (o1 == o2) {
						return 0;
					}
					Date o1End = o1.getEndTime();
					Date o2End = o2.getEndTime();
					if (o1End.before(o2End)) {
						return -1;
					} else {
						return 1;
					}
				}
			});

			Map<String, Object> obj = new HashMap<>();
			obj.put("paper", dto);
			obj.put("records", submitted);
			return MobileResult.success("SUCCESS", obj);
		} else {
			return MobileResult.error("试卷数据错误", null);
		}
	}

	/**
	 * 作答页面
	 */
	@Aop("txDb")
	public Map<String, Object> answer(final PaperEntryForm form) {
		String token = form.getToken();
		Integer userId = RedissionUserCache.getUserId(token);
		Integer paperId = form.getPaperId();

		//如果作答信息不存在而且没用超过设定的作答次数,则新增一条作答信息
		PaperEntity paper = dbDao.fetch(PaperEntity.class, Cnd.where("id", "=", paperId));
		if (Util.isEmpty(paper)) {
			return MobileResult.error("试卷不存在", null);
		}

		Date now = DateUtil.nowDate();
		Date startDate = paper.getStartDate();
		if (Util.isEmpty(startDate)) {
			return MobileResult.error("考试开始时间不能为空", null);
		} else {
			if (now.before(startDate)) {
				return MobileResult.error("考试还未开始，请耐心等待", null);
			}
		}

		Date endTime = paper.getEndTime();
		if (Util.isEmpty(endTime)) {
			return MobileResult.error("考试过期时间不能为空", null);
		} else {
			//考试截止时间
			if (endTime.before(now)) {
				return MobileResult.error("考试已过期", null);
			}
		}

		//所有作答记录
		Map<String, Object> rmap = allRecord(form);
		@SuppressWarnings("unchecked")
		List<PaperAnswerInfoEntity> answerRecords = (List<PaperAnswerInfoEntity>) rmap.get(MobileResult.DATA_KEY);

		//已提交的次数
		int commitCount = 0;
		//未提交的作答记录
		List<PaperAnswerInfoEntity> uncommitRecords = new ArrayList<>();
		if (!Util.isEmpty(answerRecords)) {
			for (PaperAnswerInfoEntity r : answerRecords) {
				Integer stat = r.getStatus();
				if (AnswerStatusEnum.uncommit.intKey() == stat || AnswerStatusEnum.start.intKey() == stat) {
					uncommitRecords.add(r);
				} else {
					commitCount++;
				}
			}
		}

		PaperAnswerInfoEntity currRecord = null;
		if (!Util.isEmpty(uncommitRecords)) {
			//已存在未提交的答题记录
			if (uncommitRecords.size() > 1) {
				return MobileResult.error("未提交的作答记录最多只能有一条", null);
			} else {
				currRecord = uncommitRecords.get(0);

				//当次作答记录的截止时间
				Date answerEndTime = QuestionUtil.calAnswerEndTime(paper, currRecord);
				if (now.after(answerEndTime)) {
					if (null != currRecord) {
						submitAnswerRecord(currRecord, answerEndTime);
					}
					return MobileResult.error("本次考试已结束", null);
				}
			}
		}

		//如果没有未提交的作答记录,则新增一条
		if (Util.isEmpty(currRecord)) {
			Integer answerNumber = paper.getAnswerNumber();
			if (Util.isEmpty(answerNumber)) {
				return MobileResult.error("试卷可作答次数不能为空", null);
			} else {
				if (commitCount >= answerNumber) {
					return MobileResult.error("试卷提交次数(" + answerNumber + ")已经用完", null);
				} else {
					currRecord = new PaperAnswerInfoEntity();
					currRecord.setAnswerStatue(YesOrNoEnum.NO.intKey());
					currRecord.setStatus(AnswerStatusEnum.start.intKey());
					currRecord.setStudentId(userId);
					currRecord.setExamId(paperId);
					currRecord.setStartTime(now);
					currRecord.setObjectScore(0.0d);
					currRecord.setSubjectScore(0.0d);
					currRecord = dbDao.insert(currRecord);
				}
			}
		}

		//查询作答页面基本信息
		Sql paperSql = Sqls.create(sqlManager.get("minapp_paper_answer_Info"));
		paperSql.setParam("paperId", paperId);
		List<PaperAnswerDto> lst = DbSqlUtil.query(dbDao, PaperAnswerDto.class, paperSql);
		if (!Util.isEmpty(lst) && lst.size() == 1) {
			PaperAnswerDto dto = lst.get(0);
			//计算倒计时
			Date currStartTime = currRecord.getStartTime();
			calLeftSeconds(dto, currStartTime);
			//答题记录的id
			dto.setRecordId(currRecord.getId());

			//2 查询试卷的试题信息
			Sql sql = Sqls.create(sqlManager.get("minapp_paper_answer_questionList"));
			sql.setParam("userId", userId);
			sql.setParam("paperId", paperId);

			//试题集合
			Set<PaperAnswerQuestionDto> questList = new TreeSet<>();
			//准备一个Map存放试题id对应的选项
			Map<Integer, List<PaperQuestionOptionDto>> optMap = new HashMap<>();
			List<Record> dbRecords = DbSqlUtil.query(dbDao, sql, null);
			if (!Util.isEmpty(dbRecords)) {
				for (Record r : dbRecords) {
					PaperAnswerQuestionDto q = BeanUtil.map2Object(r, PaperAnswerQuestionDto.class);
					questList.add(q);

					PaperQuestionOptionDto opt = BeanUtil.map2Object(r, PaperQuestionOptionDto.class);
					Integer optQid = opt.getOptQid();
					if (!Util.isEmpty(optQid)) {
						List<PaperQuestionOptionDto> optLst = optMap.get(optQid);
						if (Util.isEmpty(optLst)) {
							optLst = new ArrayList<>();
							optLst.add(opt);
							optMap.put(optQid, optLst);
						} else {
							optLst.add(opt);
						}
					}
				}
			}

			//设置试题的选项
			if (!Util.isEmpty(questList)) {
				questList
						.forEach(q -> {
							String stem = q.getStem();
							stem = WeappHtmlUtil.clearHtml(stem, "p");
							if (QuestionTypeEnum.FILLIN.intKey() == q.getQuestionType()) {
								FillInStemDto fd = QuestionUtil.fillInStemReplace(stem, q.getAnswerContent());
								stem = fd.getStem();
								q.setFillList(fd.getFillList());
							}
							q.setStem(stem);

							List<PaperQuestionOptionDto> optLst = optMap.get(q.getId());
							if (!Util.isEmpty(optLst)) {
								Collections.sort(optLst, new Comparator<PaperQuestionOptionDto>() {
									@Override
									public int compare(PaperQuestionOptionDto o1, PaperQuestionOptionDto o2) {
										return CompareUtil.safeCompareToGt(o1.getOpt(), o2.getOpt());
									}
								});
								q.setOptLst(optLst);
								q.setOptActive();
							}

							//材料题的文件
							Integer questionType = q.getQuestionType();
							if (!Util.isEmpty(questionType)
									&& QuestionTypeEnum.MATERIALSCIENCE.intKey() == questionType) {
								List<PaperFileQuestionEntity> questFiles = dbDao.query(PaperFileQuestionEntity.class,
										Cnd.where("questionId", "=", q.getId()), null);
								q.setQuestFiles(questFiles);

								Integer answerId = q.getAnswerId();
								if (!Util.isEmpty(answerId)) {
									List<PaperQuestionAnswerFileEntity> answerFiles = dbDao.query(
											PaperQuestionAnswerFileEntity.class, Cnd.where("answerId", "=", answerId),
											null);
									q.setAnswerFiles(answerFiles);
								}
							}
						});
			}

			dto.setQuestList(questList);
			return MobileResult.success("SUCCESS", dto);
		} else {
			return MobileResult.error("试卷数据错误", null);
		}
	}

	/**
	 * 作答详情
	 */
	public Map<String, Object> answerDetail(final PaperAnswerDetailForm form) {
		//答题记录id
		Integer recordId = form.getRecordId();

		//查询作答页面基本信息
		Sql hSql = Sqls.create(sqlManager.get("minapp_paper_answerDetail_Info"));
		hSql.setParam("recordId", recordId);

		//正在答题或未提交的
		hSql.setParam("start", AnswerStatusEnum.start.intKey());
		hSql.setParam("uncommit", AnswerStatusEnum.uncommit.intKey());
		List<PaperAnswerDto> lst = DbSqlUtil.query(dbDao, PaperAnswerDto.class, hSql);
		if (!Util.isEmpty(lst) && lst.size() == 1) {
			PaperAnswerDto dto = lst.get(0);

			//2 查询试题信息
			Sql sql = Sqls.create(sqlManager.get("minapp_paper_answerDetail_questionList"));
			sql.setParam("recordId", recordId);

			//试题集合
			Set<PaperAnswerQuestionDto> questList = new TreeSet<>();
			//准备一个Map存放试题id对应的选项
			Map<Integer, List<PaperQuestionOptionDto>> optMap = new HashMap<>();
			List<Record> records = DbSqlUtil.query(dbDao, sql, null);
			if (!Util.isEmpty(records)) {
				for (Record r : records) {
					PaperAnswerQuestionDto q = BeanUtil.map2Object(r, PaperAnswerQuestionDto.class);
					questList.add(q);

					PaperQuestionOptionDto opt = BeanUtil.map2Object(r, PaperQuestionOptionDto.class);
					Integer optQid = opt.getOptQid();
					if (!Util.isEmpty(optQid)) {
						List<PaperQuestionOptionDto> optLst = optMap.get(optQid);
						if (Util.isEmpty(optLst)) {
							optLst = new ArrayList<>();
							optLst.add(opt);
							optMap.put(optQid, optLst);
						} else {
							optLst.add(opt);
						}
					}
				}
			}

			//设置试题的属性
			if (!Util.isEmpty(questList)) {
				questList.forEach(q -> {
					List<PaperQuestionOptionDto> optLst = optMap.get(q.getId());
					if (!Util.isEmpty(optLst)) {
						Collections.sort(optLst, new Comparator<PaperQuestionOptionDto>() {
							@Override
							public int compare(PaperQuestionOptionDto o1, PaperQuestionOptionDto o2) {
								return CompareUtil.safeCompareToGt(o1.getOpt(), o2.getOpt());
							}
						});
						q.setOptLst(optLst);
					}
					q.setQuestAnswer();

					//材料题的文件
						Integer questionType = q.getQuestionType();
						if (!Util.isEmpty(questionType) && QuestionTypeEnum.MATERIALSCIENCE.intKey() == questionType) {
							List<PaperFileQuestionEntity> questFiles = dbDao.query(PaperFileQuestionEntity.class,
									Cnd.where("questionId", "=", q.getId()), null);

							List<PaperQuestionAnswerFileEntity> answerFiles = dbDao.query(
									PaperQuestionAnswerFileEntity.class, Cnd.where("answerId", "=", recordId), null);

							q.setQuestFiles(questFiles);
							q.setAnswerFiles(answerFiles);
						}
					});
			}

			dto.setQuestList(questList);
			return MobileResult.success("SUCCESS", dto);
		} else {
			return MobileResult.error("试卷数据错误", null);
		}
	}

	/**
	 * 查询已提交的答题记录
	 */
	public Map<String, Object> submittedRecord(final PaperEntryForm form) {
		String token = form.getToken();
		Integer userId = RedissionUserCache.getUserId(token);
		Integer paperId = form.getPaperId();

		SqlExpressionGroup g = new SqlExpressionGroup();
		g.and("status", "=", AnswerStatusEnum.finish.intKey()).or("status", "=", AnswerStatusEnum.marking.intKey());

		//查询所有答题记录
		List<PaperAnswerInfoEntity> data = dbDao.query(PaperAnswerInfoEntity.class, Cnd.where("studentId", "=", userId)
				.and("examId", "=", paperId).and(g).desc("endTime"), null);
		return MobileResult.success("SUCCESS", data);
	}

	/**
	 * 查询所有答题记录
	 */
	public Map<String, Object> allRecord(final PaperEntryForm form) {
		String token = form.getToken();
		Integer userId = RedissionUserCache.getUserId(token);
		Integer paperId = form.getPaperId();
		List<PaperAnswerInfoEntity> data = dbDao.query(PaperAnswerInfoEntity.class, Cnd.where("studentId", "=", userId)
				.and("examId", "=", paperId), null);
		return MobileResult.success("SUCCESS", data);
	}

	/**
	 * 保存试题作答信息
	 * <p>
	 * 注意:填空题的答案保存为整个题干+答案(答案保存在input标签中)
	 */
	@Aop("txDb")
	public Map<String, Object> saveQuestionAnswer(final PaperSaveQuestAnswerForm form) {
		String token = form.getToken();
		Integer userId = RedissionUserCache.getUserId(token);

		Integer questId = form.getQuestId();
		Integer recordId = form.getRecordId();
		if (!Util.isEmpty(recordId)) {
			PaperAnswerInfoEntity record = dbDao.fetch(PaperAnswerInfoEntity.class, Cnd.where("id", "=", recordId));
			if (Util.isEmpty(record)) {
				return MobileResult.error("答题记录不存在", null);
			} else {
				PaperQuestionEntity quest = dbDao.fetch(PaperQuestionEntity.class, Cnd.where("id", "=", questId));
				if (Util.isEmpty(quest)) {
					return MobileResult.error("试题不存在", null);
				}

				//新增或更新
				PaperQuestionAnswerEntity qa = dbDao.fetch(
						PaperQuestionAnswerEntity.class,
						Cnd.where("studentId", "=", userId).and("questionId", "=", questId)
								.and("answerId", "=", recordId));

				String answerContent = QuestionUtil.answerNormalize(form.getAnswerContent());
				Integer questionType = quest.getQuestionType();
				if (Util.isEmpty(questionType)) {
					return MobileResult.error("题型不能为空", null);
				} else {
					//填空题的答案保存为整个题干+答案(答案保存在input标签中)
					if (QuestionTypeEnum.FILLIN.intKey() == questionType) {
						String stem = quest.getStem();
						answerContent = QuestionUtil.fillInAnswerContent(stem, answerContent);
					}
				}

				//保存试题得分
				Integer objective = quest.getObjective();
				if (Util.isEmpty(objective)) {
					return MobileResult.error("试题主客观类型不能为空", null);
				}

				Double actualScore = 0d;
				boolean correct = false;
				if (ObjectiveEnum.ObjectiveType.intKey() == quest.getObjective()) {
					String answerOption = quest.getAnswerOption();
					//如果回答正确则更新客观题得分
					if (QuestionUtil.objectiveCorrect(answerOption, answerContent)) {
						actualScore += quest.getScore();
						correct = true;
					}
				}

				if (Util.isEmpty(qa)) {
					qa = new PaperQuestionAnswerEntity();
					qa.setStudentId(userId);
					qa.setAnswerId(recordId);
					qa.setQuestionId(questId);
					qa.setScore(quest.getScore());
					qa.setPaperId(record.getExamId());
					qa.setAnswerContent(answerContent);
					qa.setActualScore(actualScore);

					qa = dbDao.insert(qa);
				} else {
					Integer anId = qa.getId();
					dbDao.update(PaperQuestionAnswerEntity.class,
							Chain.make("answerContent", answerContent).add("actualScore", actualScore),
							Cnd.where("id", "=", anId));
				}

				PaperSaveAnswerDto data = new PaperSaveAnswerDto();
				BeanUtil.copyProperties(qa, data);
				data.setCorrect(correct);
				return MobileResult.success("SUCCESS", data);
			}
		} else {
			return MobileResult.error("答题记录id不能为空", null);
		}
	}

	/**
	 * 查看提交结果页面
	 */
	public Map<String, Object> result(final PaperResultForm form) {
		Integer recordId = form.getRecordId();
		PaperAnswerInfoEntity data = dbDao.fetch(PaperAnswerInfoEntity.class, Cnd.where("id", "=", recordId));
		return MobileResult.success("SUCCESS", data);
	}

	/**
	 * 提交试卷
	 */
	@Aop("txDb")
	public Map<String, Object> commitPaper(final PaperCommitForm form) {
		String token = form.getToken();
		Integer userId = RedissionUserCache.getUserId(token);

		Integer recordId = form.getRecordId();
		if (!Util.isEmpty(recordId)) {
			PaperAnswerInfoEntity record = dbDao.fetch(PaperAnswerInfoEntity.class, Cnd.where("id", "=", recordId));
			if (Util.isEmpty(record)) {
				return MobileResult.error("答题记录不存在", null);
			} else {
				Integer status = record.getStatus();
				if (Util.isEmpty(status)) {
					return MobileResult.error("数据错误，答题记录状态不能为空", null);
				} else {
					if (AnswerStatusEnum.finish.intKey() == status || AnswerStatusEnum.marking.intKey() == status) {
						return MobileResult.error("该次考试已交卷，请勿重复提交", null);
					}
				}

				//1,先保存用户的答案
				List<PaperAnswerQuestionDto> candidates = form.getQuestList();
				if (!Util.isEmpty(candidates)) {
					List<PaperQuestionAnswerEntity> toBeAdd = new ArrayList<>();
					List<PaperQuestionAnswerEntity> toBeUpdate = new ArrayList<>();

					candidates.forEach(q -> {
						Integer questId = q.getId();
						Double actualScore = 0d;
						String answerContent = QuestionUtil.answerNormalize(q.getAnswerContent());

						Integer questionType = q.getQuestionType();
						if (QuestionTypeEnum.FILLIN.intKey() == questionType) {
							String stem = QuestionUtil.getStem4Storage(q.getStem());
							answerContent = QuestionUtil.fillInAnswerContent(stem, answerContent);
						}

						if (ObjectiveEnum.ObjectiveType.intKey() == q.getObjective()) {
							String answerOption = q.getAnswerOption();
							if (QuestionUtil.objectiveCorrect(answerOption, answerContent)) {
								actualScore += q.getScore();
							}
						}

						PaperQuestionAnswerEntity qa = dbDao.fetch(
								PaperQuestionAnswerEntity.class,
								Cnd.where("questionId", "=", questId).and("studentId", "=", userId)
										.and("answerId", "=", recordId));
						if (!Util.isEmpty(qa)) {
							//更新答案和分数
							qa.setAnswerContent(answerContent);
							qa.setActualScore(actualScore);
							toBeUpdate.add(qa);
						} else {
							qa = new PaperQuestionAnswerEntity();
							qa.setStudentId(userId);
							qa.setAnswerId(recordId);
							qa.setQuestionId(questId);
							qa.setScore(q.getScore());
							qa.setPaperId(record.getExamId());
							qa.setAnswerContent(answerContent);
							qa.setActualScore(actualScore);
							toBeAdd.add(qa);
						}
					});
					if (!Util.isEmpty(toBeAdd)) {
						dbDao.insert(toBeAdd);
					}

					if (!Util.isEmpty(toBeUpdate)) {
						dbDao.update(toBeUpdate, "answerContent", "actualScore");
					}
				}

				//2 客观题判卷
				objectiveReview(userId, recordId);
			}
		}
		return MobileResult.success("SUCCESS", null);
	}

	/**
	 * 删除材料题答案附件
	 */
	public Map<String, Object> deleteFile(@Param("..") final PaperDeleteAnswerFileForm form) {
		Integer id = form.getId();
		dbDao.delete(PaperQuestionAnswerFileEntity.class, id);
		return MobileResult.success("SUCCESS", null);
	}

	/**
	 * 材料题文件上传
	 * 上传类型:1 图片 2 文件
	 * @param upType 上传类型
	 * @throws IOException 
	 */
	@Aop("txDb")
	public Map<String, Object> uploadFile(final TempFile tf, final PaperAnswerFileForm form, Integer upType)
			throws IOException {

		//文件后缀
		String fName = tf.getFile().getName();
		String suffix = Files.getSuffix(fName);

		String fileName = form.getFileName();
		Integer resourceType = null;
		String fileUrl = null;
		if (QuestionUtil.UPLOAD_TYPE_IMG == upType) {
			resourceType = QuestionAnswerResourceTypeEnum.IMG.intKey();
			//图片
			InputStream inputStream = tf.getInputStream();
			suffix = suffix.substring(1);
			fileUrl = qiniuDownloadService.uploadImage(inputStream, suffix, null);
		} else if (QuestionUtil.UPLOAD_TYPE_VIDEO == upType) {
			//视频
			resourceType = QuestionAnswerResourceTypeEnum.VIDEO.intKey();
			AliyunVideoDesc vd = new AliyunVideoDesc();

			vd.setTitle(fileName);
			File file = tf.getFile();
			fileUrl = aliyunVideoUploadService.uploadFile(vd, file.getAbsolutePath());
		}

		else if (QuestionUtil.UPLOAD_TYPE_FILE == upType) {
			// TODO 文件+音频(小程序如何做?)
			resourceType = QuestionAnswerResourceTypeEnum.FILE.intKey();
		}

		if (!Util.isEmpty(fileUrl)) {
			String token = form.getToken();
			Integer userId = RedissionUserCache.getUserId(token);

			Integer questionId = form.getQuestionId();
			Integer recordId = form.getRecordId();

			PaperQuestionEntity quest = dbDao.fetch(PaperQuestionEntity.class, Cnd.where("id", "=", questionId));
			if (Util.isEmpty(quest)) {
				return MobileResult.error("试题不存在", null);
			}

			PaperAnswerInfoEntity record = dbDao.fetch(PaperAnswerInfoEntity.class, Cnd.where("id", "=", recordId));
			if (Util.isEmpty(record)) {
				return MobileResult.error("答题记录不存在", null);
			}

			//如果试题的答案还未保存则新增一条答案
			PaperQuestionAnswerEntity qa = dbDao.fetch(
					PaperQuestionAnswerEntity.class,
					Cnd.where("studentId", "=", userId).and("questionId", "=", questionId)
							.and("answerId", "=", recordId));
			if (Util.isEmpty(qa)) {
				qa = new PaperQuestionAnswerEntity();
				qa.setStudentId(userId);
				qa.setAnswerId(recordId);
				qa.setQuestionId(questionId);
				qa.setScore(quest.getScore());
				qa.setPaperId(record.getExamId());
				qa = dbDao.insert(qa);
			}

			Integer answerId = qa.getId();
			Date now = DateUtil.nowDate();
			PaperQuestionAnswerFileEntity af = new PaperQuestionAnswerFileEntity();
			af.setAnswerId(answerId);
			af.setCreateDate(now);
			af.setResourceName(fileName);
			af.setResourceValue(fileUrl);
			af.setResourceType(resourceType);
			af = dbDao.insert(af);
			return MobileResult.success("success", af);
		} else {
			return MobileResult.error("上传失败", null);
		}
	}

	/**
	 * 客观题判卷
	 * <p>
	 *	因为'保存试题'和'提交试卷'的时候已经保存了用户客观题的得分，因此这里只保存客观题总得分即可
	 */
	@Aop("txDb")
	private void objectiveReview(Integer userId, Integer recordId) {
		//设置客观题分数 并置为阅卷状态
		Sql sql = Sqls.create(sqlManager.get("minapp_paper_objective_review"));
		sql.setParam("objective", ObjectiveEnum.ObjectiveType.intKey());
		sql.setParam("userId", userId);
		sql.setParam("recordId", recordId);
		List<PaperObjectiveReviewDto> lst = DbSqlUtil.query(dbDao, PaperObjectiveReviewDto.class, sql);

		Double objectScore = 0.0d;
		if (!Util.isEmpty(lst)) {
			for (PaperObjectiveReviewDto dto : lst) {
				String answerOption = dto.getAnswerOption();
				String answerContent = dto.getAnswerContent();
				if (QuestionUtil.objectiveCorrect(answerOption, answerContent)) {
					objectScore += dto.getScore();
				}
			}
		}
		dbDao.update(
				PaperAnswerInfoEntity.class,
				Chain.make("objectScore", objectScore).add("status", AnswerStatusEnum.finish.intKey())
						.add("endTime", DateUtil.nowDate()), Cnd.where("id", "=", recordId));
	}

	/**
	 * 计算倒计时
	 * <p>
	 * 开始时间的计算方式:
	 * 如果有未提交的答题记录，则开始时间为该记录的开始时间，否则为试卷的开始考试时间
	 * @param startDate 开始时间
	 */
	private void calLeftSeconds(PaperAnswerDto dto, Date startDate) {
		if (!Util.isEmpty(startDate)) {
			Date now = DateUtil.nowDate();
			long answerTime = dto.getAnswerTime() * 60;
			long millis = DateUtil.millisBetween(startDate, now);
			long leftSeconds = answerTime - (millis / 1000);
			if (leftSeconds < 0) {
				leftSeconds = 0;
			}
			if (leftSeconds > answerTime) {
				leftSeconds = answerTime;
			}
			dto.setLeftSeconds(leftSeconds);
		}
	}

	/**
	 * 将已结束,未提交的记录更新为已提交
	 * <p>
	 * @param unsubmit 未提交的答题记录
	 * @param answerEndTime 提交时间
	 */
	@Aop("txDb")
	private void submitAnswerRecord(PaperAnswerInfoEntity unsubmit, Date answerEndTime) {
		unsubmit.setStatus(AnswerStatusEnum.finish.intKey());
		unsubmit.setEndTime(answerEndTime);
		dbDao.update(unsubmit, "status", "endTime");
	}

}